/**************************************************************
 * ui_gtkosd.c - 
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is  free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
**************************************************************/

#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <gtk/gtk.h>
#include <glade/glade.h>
#include <libnotify/notify.h>

#include "common.h"
#include "configfile.h"
#include "ui.h"
#include "client.h"
#include "fetchlyric.h"
#include "lyricparse.h"
#include "encoding_convert.h"
#include "argproc.h"
#include "metainfo.h"
#include "ghosd.h"


static gboolean prompt_for_select = FALSE;

struct ui_data{
  GtkStatusIcon *si;  				/* GtkStatusIcon */
  Ghosd *ghosd;
  PangoLayout *layout;
  struct client *client;
  fetchlyric_t *fl;
  //  metainfo_t *mi;
  config_t *config;
  lyric_t *lyric;
  gchar *playing_file;
  gchar *font;
  gboolean font_changed;
  GThread *thread;
  gint magic;
  gint backend_status;
  gboolean cancel;
  gboolean on_show;
  gboolean done_show;
  //const gchar *lyric;
  gboolean no_lyric;
  gboolean hide;
  double r, g, b, alpha;
  double shadow_alpha;
  gboolean with_shadow;
  gint x, y;  					/* lyric position */
  gboolean autoselect;
  gboolean manual_search_available;
  gint select_timeout;
};


static volatile gint xmms2_lock = 0;

static gboolean __is_illegal(gchar c, const gchar *str)
{
  while(*str)
	if(c == *str++)
	  return TRUE;

  return FALSE;
}

static gchar  *__sanify(gchar *str)
{
  gchar *p, *i, *j;

  if(! str)
    return NULL;

  p = g_new0(gchar, strlen(str) + 1);
  i = p;
  j = str;
  while(*j)  {
    if(! __is_illegal(*j, " \t~`!@#$%^&*()-_+=|\\{}[]:\";\'<>?,./"))  {
	  *i = *j;
	  i++;
	}
    j++;
  }

  strcpy(str, p);
  g_free(p);

  return str;
}


static void notify_user(struct ui_data *ui_data, const gchar *summary,
						gint timeout, const gchar *fmt, ...)
{
  va_list ap;
  gchar *text;
  static NotifyNotification *noti = NULL;

  va_start(ap, fmt);
  text = g_strdup_vprintf(fmt, ap);
  if(! notify_is_initted())
	notify_init(PROG_NAME);
  if(! noti)  {
	noti = notify_notification_new(summary, text, ICON_PATH, NULL);
	notify_notification_attach_to_status_icon(noti, ui_data->si);
  }
  if(! timeout)
	timeout = NOTIFY_EXPIRES_DEFAULT;
  notify_notification_set_timeout(noti, timeout);
  notify_notification_show(noti, NULL);
}

static lyric_t *prompt_pick_lyric(GSList *list,
								  const gchar *title,
								  const gchar *artist,
								  struct ui_data *ui_data)
{
  GSList *iter = list;
  struct lyric_url *info;
  gint title_len = 0, artist_len = 0, album_len = 0;
  gint index = 0, tmp, term_width;
  FILE *lyric_file;
  lyric_t *lyric = NULL;
  gchar *_artist;

  if(! list)
    return NULL;

  if(! ui_data->autoselect)  {
	notify_user(ui_data, "Lyric Select", ui_data->select_timeout * 1000,
				"%s %s %s", title, artist ? " - " : "", artist ?: "", NULL);
	//	gtk_status_icon_set_blinking(ui_data->si, TRUE);
	return NULL;
  }

  if(index == 0)  {
	iter = list;
	do{
	  info = (struct lyric_url *)iter->data;
	  if(artist && info->artist)  {
		_artist = strdup(info->artist);
		__sanify(_artist);
		if(! strcasecmp(artist, _artist))  {
		  g_free(_artist);
		  break;
		}
		g_free(_artist);
	  }
	}while(iter = g_slist_next(iter));
	if(! iter)
	  iter = list;
  }else
	iter = g_slist_nth(list, index - 1);

  if(iter)  {
    lyric_file = fetch_lyric_file_next(iter);
    if(lyric_file)  {
      lyric = lyricparse_parse_lyric(lyric_file);
	  if(! lyric)
		printf("BUG: FAILED PARSING LYRIC FILE\n");
      fclose(lyric_file);
      return lyric;
    }
  }
  return NULL;
}


static lyric_t *__pick_right_lyric(struct ui_data *ui_data, const gchar *title, const gchar *artist)
{
  fetchlyric_t *fl = ui_data->fl;
  lyric_t *lyric = NULL;
  FILE *lyric_file;
  GSList *list;

  list = fetch_lyric_get_list_from(fl, "localdir", title, artist);
  if(! list)
    list = fetch_lyric_get_list(fl, title, artist);

  if(! list)
    return NULL;

  if(g_slist_length(list) > 1)
    lyric = prompt_pick_lyric(list, title, artist, ui_data);
  else  {
    lyric_file = fetch_lyric_file_next(list);
    if(lyric_file)  {
      lyric = lyricparse_parse_lyric(lyric_file);
      fclose(lyric_file);
    }
  }

  if(! fetch_lyric_free_list(list)) {
    DBGMSG("failed to free lyric list:%s:%d\n", __FILE__, __LINE__);
  }

  return lyric;
}



static lyric_t *pick_right_lyric(struct ui_data *ui_data, const gchar *expect_encoding)
{
  struct client *client = ui_data->client;
  fetchlyric_t *fl = ui_data->fl;
  const gchar *artist = NULL, *title = NULL;
  const gchar *x_artist, *x_title; 
  const gchar *path = NULL;
  lyric_t *lyric = NULL;
  metainfo_t *mi = NULL;

  while(( path = (client->get_music_file)(client->c_data)) == (const gchar *)-1); /* FUCX it! */
  DBGMSG("pick_right_lyric:path:%#p\n", path);
  DBGMSG("parse metainfo of %s\n", path ?: "(NULL)");
  if(path)  {
	mi = metainfo_get_metainfo(path, expect_encoding, 0);
	if(mi)  {
	  x_artist = metainfo_get_artist(mi);
	  x_title = metainfo_get_title(mi);
	  if(x_title)  {
		artist = g_strdup(x_artist);
		title = g_strdup(x_title);
		DBGMSG("extracted title info: %s\n", title);
		DBGMSG("extracted artist info: %s\n", artist ?: "(NULL)");
	  }
	}
  }

  if(! title)  {
	DBGMSG("failed to get meta info via metainfo, fall to media player.\n");
	if(mi)
	  metainfo_put_metainfo(mi);


	x_artist = (client->get_artist)(client->c_data);
	if(x_artist)
	  if(artist = zh_convert(x_artist, strlen(x_artist), NULL, expect_encoding))  {
		DBGMSG(" artist encoding convert ok: %s : %s\n", x_artist, artist);
		g_free((void *)x_artist);
	  }else  {
		printf("** failed to sanify artist encoding: %s\n", x_artist);
		artist = x_artist;
	  }

	x_title = (client->get_title)(client->c_data);
	if(x_title)
	  if(title = zh_convert(x_title, strlen(x_title), NULL, expect_encoding))  {
		DBGMSG("title encoding convert ok: %s : %s\n", x_title, title);
		g_free((void *)x_title);
	  }else  {
		printf("** failed to sanify title encoding: %s\n", x_title);
		title = x_title;
	  }

	if(! title)  {
	  g_free((void *)artist);
	  return NULL;
	}
  }

  lyric = __pick_right_lyric(ui_data, title, artist);

  g_free((void *)title);
  g_free((void *)artist);

  DBGMSG("get lyric %s\n", lyric ? "OK" : "Failed");

  return lyric;
}


static void response_dialog(GtkDialog *dialog, gpointer data)
{
  gtk_widget_destroy(GTK_WIDGET(data));
}

static void dialog_warn(const gchar *fmt, ...)
{
  GtkWidget *w;
  va_list ap;
  gchar *text;
  gint ret;

  va_start(ap, fmt);
  text = g_strdup_vprintf(fmt, ap);
  w = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
							 GTK_BUTTONS_CLOSE, text);
  //  g_signal_connect(G_OBJECT(w), "response", G_CALLBACK(response_dialog), w);
  gtk_dialog_run(GTK_DIALOG(w));
  gtk_widget_destroy(w);
  va_end(ap);
  g_free(text);
}


static void prev_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  struct timespec ts = { 0, 15000000 };

  while(xmms2_lock)
	nanosleep(&ts, NULL);
  xmms2_lock++;
  (ui_data->client->prev)(ui_data->client->c_data);
  xmms2_lock--;
}


static void toggle_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  struct timespec ts = { 0, 15000000 };
  GtkWidget *image;
  GtkWidget *label;
  gint status;

  while(xmms2_lock)
	nanosleep(&ts, NULL);
  xmms2_lock++;
  (ui_data->client->toggle)(ui_data->client->c_data);
  status = (ui_data->client->status)(ui_data->client->c_data);
  xmms2_lock--;

  label = gtk_bin_get_child(GTK_BIN(item));
  if(status == PLAYER_STATUS_PLAY)  {
	image = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PAUSE, GTK_ICON_SIZE_MENU);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	gtk_label_set_text(GTK_LABEL(label), "pause");
  }else{
	image = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_MENU);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	gtk_label_set_text(GTK_LABEL(label), "play");
  }
}


static void next_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  struct timespec ts = { 0, 15000000 };

  while(xmms2_lock)
	nanosleep(&ts, NULL);
  xmms2_lock++;
  (ui_data->client->next)(ui_data->client->c_data);
  xmms2_lock--;
}


struct manual_search_data{
  struct ui_data *ui_data;
  GladeXML *gxml;
  GtkWidget *mwin;
  GtkWidget *treeview;
  GtkWidget *title;
  GtkWidget *artist;
  GtkWidget *search;
  GtkWidget *info;
  GtkWidget *filepath;
  GtkWidget *preview;
  GtkWidget *save;
  GtkWidget *saveas;
  GtkWidget *close;
  GtkWidget *apply;
  GtkWidget *ok;
  metainfo_t *mi;
  GSList *lyriclist;
};


enum {
  INDEX_ITEM,
  TITLE_ITEM,
  ARTIST_ITEM,
  ALBUM_ITEM,
  N_ITEMS
};

static void init_tree_view(struct manual_search_data *mdata)
{
  GtkCellRenderer *render;
  GtkTreeViewColumn *column;
  GtkListStore *store;
  gint i = 0;


  render = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes("INDEX", render,
													"text", INDEX_ITEM, NULL);

  gtk_tree_view_append_column(GTK_TREE_VIEW(mdata->treeview), column);
  render = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes("TITLE", render,
													"text", TITLE_ITEM, NULL);

  gtk_tree_view_append_column(GTK_TREE_VIEW(mdata->treeview), column);
  render = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes("ARTIST", render,
													"text", ARTIST_ITEM, NULL);

  gtk_tree_view_append_column(GTK_TREE_VIEW(mdata->treeview), column);
  render = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes("ALBUM", render,
													"text", ALBUM_ITEM, NULL);

  gtk_tree_view_append_column(GTK_TREE_VIEW(mdata->treeview), column);

  store = gtk_list_store_new(N_ITEMS, G_TYPE_INT, G_TYPE_STRING,
							 G_TYPE_STRING,  G_TYPE_STRING);
  gtk_tree_view_set_model(GTK_TREE_VIEW(mdata->treeview),
						  GTK_TREE_MODEL(store));

  g_object_unref(store);
}


static void clear_tree_view(struct manual_search_data *mdata)
{
  GtkListStore *store;

  store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(mdata->treeview)));
  gtk_list_store_clear(store);
}


static void fill_tree_view(struct manual_search_data *mdata)
{
  GSList *lyriclist = mdata->lyriclist;
  struct lyric_url *lyric_url;
  GtkListStore *store;
  GtkTreeIter iter;
  gint i = 1;

  store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(mdata->treeview)));

#ifndef NDEBUG
  while(lyriclist)  {
	lyric_url = (struct lyric_url *)lyriclist->data;
	printf("INFO: TITLE:%s ARTIST:%s ALBUM:%s\n", lyric_url->title ?: "",
		   lyric_url->artist ?: "", lyric_url->album ?:"");
	lyriclist = g_slist_next(lyriclist);
  }
  lyriclist = mdata->lyriclist;
#endif

  while(lyriclist)  {
	lyric_url = (struct lyric_url *)lyriclist->data;
	gtk_list_store_append(store, &iter);
	gtk_list_store_set(store, &iter, INDEX_ITEM, i++,
					   TITLE_ITEM, lyric_url->title ?: "",
					   ARTIST_ITEM, lyric_url->title ?: "",
					   ALBUM_ITEM, lyric_url->album ?: "",
					   -1);
	lyriclist = g_slist_next(lyriclist);
  }
}

static gboolean manualsearch_delete_event_cb(GtkWidget *w, GdkEvent *e, gpointer data)
{
  struct manual_search_data *mdata = (struct manual_search_data *)data;

  if(mdata->mi)  {
	metainfo_put_metainfo(mdata->mi);
	mdata->mi = NULL;
  }
  if(mdata->lyriclist)  {
	fetch_lyric_free_list(mdata->lyriclist);
	mdata->lyriclist = NULL;
  }

  gtk_widget_hide_all(mdata->mwin);
  return TRUE;
}


static gboolean is_empty(const gchar *str)
{
  if(! str)  return TRUE;
  while(*str)
	if(! g_unichar_isspace(*str++))
	  return FALSE;
  return TRUE;
}


static void manualsearch_search_cb(GtkButton *b, gpointer data)
{
  struct manual_search_data *mdata = (struct manual_search_data *)data;
  const gchar *title, *artist;

  title = gtk_entry_get_text(GTK_ENTRY(mdata->title));
  if(is_empty(title))  {
	dialog_warn("Title must not be empty.");
	return;
  }
  artist = gtk_entry_get_text(GTK_ENTRY(mdata->artist));
  if(mdata->lyriclist)  {
	clear_tree_view(mdata);
	fetch_lyric_free_list(mdata->lyriclist);
  }
  mdata->lyriclist = fetch_lyric_get_list(mdata->ui_data->fl, title, artist);
  if(! mdata->lyriclist)  {
	dialog_warn("No lyric searched!");
	return;
  }
  fill_tree_view(mdata);
}


static void window_quit_cb(GtkButton *b, gpointer data)
{
  gtk_widget_destroy(GTK_WIDGET(data));
}


static gint gtk_tree_view_get_index(struct manual_search_data *mdata)
{
  GtkTreeSelection *sel;
  GtkTreeModel *model;
  GtkTreeIter iter;
  gint value;

  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(mdata->treeview));
  if(! gtk_tree_selection_get_selected(sel, &model, &iter))
	return -1;
  gtk_tree_model_get(model, &iter, INDEX_ITEM, &value, -1);

  return value;
}


static void gtk_view(const gchar *title, const gchar *str)
{
  GtkWidget *win;
  GtkWidget *vbox;
  GtkWidget *ok;
  GtkWidget *label;

  win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(win), title);
  gtk_window_set_default_size(GTK_WINDOW(win), 80, 50);
  gtk_container_set_border_width(GTK_CONTAINER(win), 5);
  vbox = gtk_vbox_new(FALSE, 3);
  label = gtk_label_new(NULL);
  gtk_label_set_text(GTK_LABEL(label), str);
  gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
  ok = gtk_button_new_from_stock(GTK_STOCK_OK);
  g_signal_connect(G_OBJECT(ok), "clicked",
				   G_CALLBACK(window_quit_cb), win);
  gtk_box_pack_start(GTK_BOX(vbox), ok, FALSE, FALSE, 0);
  gtk_container_add(GTK_CONTAINER(win), vbox);
  gtk_widget_show_all(win);
}


static void manualsearch_info_cb(GtkButton *b, gpointer data)
{
  struct manual_search_data *mdata = (struct manual_search_data *)data;
  const  gchar *value;
  gchar *tmp, *last = NULL;

#define __set_info_label(attr)										\
  value = metainfo_get_##attr(mdata->mi);							\
  if(value) {														\
	tmp = last;														\
	last = g_strdup_printf("%s" #attr ": %s\n",tmp ?: "", value);	\
	g_free(tmp);													\
  }

  __set_info_label(artist);
  __set_info_label(title);
  __set_info_label(album);
  __set_info_label(year);
  __set_info_label(track);
  __set_info_label(genre);
  __set_info_label(comment);
  __set_info_label(path);

#undef __set_info_label

  gtk_view("Audio Info", last);
  g_free(last);
}


enum{
  LYRIC_TIMESTAMP_ITEM,
  LYRIC_LYRIC_ITEM,
  LYRIC_N_ITEMS
};

static void  __init_view_lyric_tree_view(GtkWidget *treeview)
{
  GtkCellRenderer *render;
  GtkTreeViewColumn *column;
  GtkListStore *store;

  render = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes("TIME", render, 
													"text", LYRIC_TIMESTAMP_ITEM,
													NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
  render = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes("LYRIC", render, 
													"text", LYRIC_LYRIC_ITEM,
													NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
  store = gtk_list_store_new(LYRIC_N_ITEMS, G_TYPE_STRING, G_TYPE_STRING);
  gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(store));
  g_object_unref(store);
}

static void gtk_view_lyric_fill_lyric(GtkWidget *treeview, lyric_t *lyric)
{
  GtkListStore *store;
  GtkTreeIter iter;
  gint now = 0, begin, end;
  gchar *timestamp, *text;

  __init_view_lyric_tree_view(treeview);
  store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)));
  for(;;)  {
	if(! lyricparse_get_lyric(lyric, &text, &begin, &end, now))
	  break;
	timestamp = g_strdup_printf("%d -> %d", begin, end ?: end + 5);
	gtk_list_store_append(store, &iter);
	gtk_list_store_set(store, &iter, LYRIC_TIMESTAMP_ITEM, timestamp,
					   LYRIC_LYRIC_ITEM, text, -1);
	g_free(timestamp);
	if(! end)
	  break;
	now = end;
  }
}


static void gtk_view_lyric(lyric_t *lyric)
{
  GtkWidget *win;
  GtkWidget *vbox;
  GtkWidget *scrollwin;
  GtkWidget *treeview;
  GtkWidget *ok;

  win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(win), "Lyric View");
  gtk_window_set_default_size(GTK_WINDOW(win), 300, 450);
  gtk_container_set_border_width(GTK_CONTAINER(win), 5);
  vbox = gtk_vbox_new(FALSE, 3);
  scrollwin = gtk_scrolled_window_new(NULL, NULL);
  treeview = gtk_tree_view_new();
  gtk_view_lyric_fill_lyric(treeview, lyric);
  ok = gtk_button_new_from_stock(GTK_STOCK_OK);
  g_signal_connect(G_OBJECT(ok), "clicked",
				   G_CALLBACK(window_quit_cb), win);
  gtk_container_add(GTK_CONTAINER(scrollwin), treeview);
  gtk_box_pack_start(GTK_BOX(vbox), scrollwin, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), ok, FALSE, FALSE, 0);
  gtk_container_add(GTK_CONTAINER(win), vbox);
  gtk_widget_show_all(win);
}

static void manualsearch_preview_cb(GtkButton *b, gpointer data)
{
  struct manual_search_data *mdata = (struct manual_search_data *)data;
  FILE *lyricfile;
  GSList *url;
  lyric_t *lyric;
  gchar *buf;
  gint index, len;
  gint now = 0, begin, end;
  gchar *text;

  index = gtk_tree_view_get_index(mdata);
  if(index == -1)
	return;
  url = g_slist_nth(mdata->lyriclist, index - 1);
  fetch_lyric_file_next(NULL);
  lyricfile = fetch_lyric_file_next(url);
  if(lyricfile)  {
	lyric = lyricparse_parse_lyric(lyricfile);
	gtk_view_lyric(lyric);
	lyricparse_release_lyric(lyric);
	fclose(lyricfile);
  }else
	dialog_warn("Failed to retrieve the lyric.\n");  
}


static void manualsearch_save_cb(GtkButton *b, gpointer data)
{
  struct manual_search_data *mdata = (struct manual_search_data *)data;
  GSList *url;
  FILE *lf;
  gint index;

  index = gtk_tree_view_get_index(mdata);
  if(index == -1)
	return;
  url = g_slist_nth(mdata->lyriclist, index - 1);
  fetch_lyric_file_next(NULL);
  lf = fetch_lyric_file_next(url);
  if(! lf)  {
	dialog_warn("failed saving lyric.");
	return;
  }

  fclose(lf);
}


static gchar *prompt_for_filename(struct manual_search_data *mdata, const gchar *default_name)
{
  GtkWidget *chooser;
  gchar *path = NULL;
  gint response;

  chooser = gtk_file_chooser_dialog_new("Choose File Name",
										NULL,
										GTK_FILE_CHOOSER_ACTION_SAVE,
										GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
										NULL);
  gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE);
  if(default_name)
	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(chooser), default_name);
  response = gtk_dialog_run(GTK_DIALOG(chooser));
  if(gtk_dialog_run(GTK_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
	path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
  gtk_widget_destroy(chooser);

  return path;
}


static gchar *get_default_lrc_name(metainfo_t *mi)
{
  const gchar *title, *artist;

  title = metainfo_get_title(mi);
  artist = metainfo_get_artist(mi);

  return g_strdup_printf("%s%s%s.lrc", title && artist ? title : "Unknown",
						 artist ? " - " : NULL, artist ?: "");

}

static void manualsearch_saveas_cb(GtkButton *b, gpointer data)
{
  struct manual_search_data *mdata = (struct manual_search_data *)data;
  gchar *path, *default_name = NULL;
  GSList *url;
  FILE *lf, *out;
  gint index;

  index = gtk_tree_view_get_index(mdata);
  if(index == -1)
	return;
  url = g_slist_nth(mdata->lyriclist, index - 1);
  fetch_lyric_file_next(NULL);
  lf = fetch_lyric_file_next(url);
  if(! lf)  {
	dialog_warn("failed retrieving lyric.");
	return;
  }

  if(mdata->mi)
	default_name = get_default_lrc_name(mdata->mi);
  path = prompt_for_filename(mdata, default_name);
  do{
	if(! path)
	  break;

	out = fopen(path, "w");
	if(! out)  {
	  dialog_warn("Failed open file : %s\n%s", path, strerror(errno));
	  break;
	}

	if(! file_copy(lf, out))  {
	  dialog_warn("failed saving file: %s", path);
	  break;
	}

	fclose(out);
  }while(0);

  g_free(default_name);
  g_free(path);
  fclose(lf);
}


static void manualsearch_close_cb(GtkButton *b, gpointer data)
{
  manualsearch_delete_event_cb(NULL, NULL, data);
}


static void manualsearch_apply_cb(GtkButton *b, gpointer data)
{
  struct manual_search_data *mdata = (struct manual_search_data *)data;
  FILE *lyricfile;
  GSList *url;
  lyric_t *lyric, *todel;
  gchar *buf;
  gint index, len;
  gint now = 0, begin, end;
  gchar *text;

  index = gtk_tree_view_get_index(mdata);
  if(index == -1)
	return;
  url = g_slist_nth(mdata->lyriclist, index - 1);
  fetch_lyric_file_next(NULL);
  lyricfile = fetch_lyric_file_next(url);
  if(lyricfile)  {
	lyric = lyricparse_parse_lyric(lyricfile);

	if(mdata->ui_data->lyric)  {
	  todel = mdata->ui_data->lyric;
	  lyricparse_release_lyric(todel);
	}
	mdata->ui_data->no_lyric = FALSE;
	mdata->ui_data->lyric = lyric;
	fclose(lyricfile);
  }else
	dialog_warn("Failed to retrieve the lyric.\n");  
}

static void manualsearch_ok_cb(GtkButton *b, gpointer data)
{

  manualsearch_apply_cb(NULL, data);
  manualsearch_delete_event_cb(NULL, NULL, data);
}

static gboolean setup_manual_search_dialog(struct manual_search_data *mdata)
{
  struct timespec ts = { 0, 15000000 };
  const gchar *artist, *title;
  gchar *filepath;
  metainfo_t *mi;
  GSList *lyriclist = NULL;

  while(xmms2_lock)
	nanosleep(&ts, NULL);
  xmms2_lock++;
  filepath = (mdata->ui_data->client->get_music_file)(mdata->ui_data->client->c_data);
  xmms2_lock--;
  mi = metainfo_get_metainfo(filepath, "utf-8", 0);
  if(! mi)
	return FALSE;

  if(mdata->mi)
	metainfo_put_metainfo(mdata->mi);
  mdata->mi = mi;

  title = metainfo_get_title(mi);
  artist = metainfo_get_artist(mi);
  if(title)
	gtk_entry_set_text(GTK_ENTRY(mdata->title), title);
  if(artist)
	gtk_entry_set_text(GTK_ENTRY(mdata->artist), artist);
  if(title)
	lyriclist = fetch_lyric_get_list(mdata->ui_data->fl, title, artist);
  if(mdata->lyriclist)  {
	getch_lyric_free_list(mdata->lyriclist);
	mdata->lyriclist = lyriclist;
  }
  clear_tree_view(mdata);
  if(lyriclist)  {
	mdata->lyriclist = lyriclist;
	fill_tree_view(mdata);
  }
 
  gtk_label_set_text(GTK_LABEL(mdata->filepath), filepath);

  return TRUE;
}


static struct manual_search_data *load_manual_search_dialog(struct ui_data *ui_data)
{
  struct manual_search_data *mdata;
  gchar *xmlpath;

  mdata = g_new0(struct manual_search_data, 1);
  xmlpath = g_build_filename(LYRICPLAYER_GLADEDIR, "manual_search.glade", NULL);
  mdata->gxml = glade_xml_new(xmlpath, NULL, NULL);
  if(! mdata->gxml)  {
	dialog_warn("failed loading glade UI: %s", xmlpath);
	g_free(xmlpath);
	return NULL;
  }

  g_free(xmlpath);

  mdata->ui_data = ui_data;
  mdata->mwin = glade_xml_get_widget(mdata->gxml, "manual_search");
  mdata->treeview = glade_xml_get_widget(mdata->gxml, "lyric_list");
  mdata->title = glade_xml_get_widget(mdata->gxml, "title");
  mdata->artist = glade_xml_get_widget(mdata->gxml, "artist");
  mdata->search = glade_xml_get_widget(mdata->gxml, "search");
  mdata->info = glade_xml_get_widget(mdata->gxml, "info");
  mdata->filepath = glade_xml_get_widget(mdata->gxml, "filepath");
  mdata->preview = glade_xml_get_widget(mdata->gxml, "preview");
  mdata->save = glade_xml_get_widget(mdata->gxml, "save");
  mdata->saveas = glade_xml_get_widget(mdata->gxml, "saveas");
  mdata->close = glade_xml_get_widget(mdata->gxml, "close");
  mdata->apply = glade_xml_get_widget(mdata->gxml, "apply");
  mdata->ok = glade_xml_get_widget(mdata->gxml, "ok");

  init_tree_view(mdata);

  g_signal_connect(G_OBJECT(mdata->mwin), "delete-event",
				   G_CALLBACK(manualsearch_delete_event_cb), mdata);
  g_signal_connect(G_OBJECT(mdata->search), "clicked",
				   G_CALLBACK(manualsearch_search_cb), mdata);
  g_signal_connect(G_OBJECT(mdata->info), "clicked",
				   G_CALLBACK(manualsearch_info_cb), mdata);
  g_signal_connect(G_OBJECT(mdata->preview), "clicked",
				   G_CALLBACK(manualsearch_preview_cb), mdata);
  g_signal_connect(G_OBJECT(mdata->save), "clicked",
				   G_CALLBACK(manualsearch_save_cb), mdata);
  g_signal_connect(G_OBJECT(mdata->saveas), "clicked",
				   G_CALLBACK(manualsearch_saveas_cb), mdata);
  g_signal_connect(G_OBJECT(mdata->close), "clicked",
				   G_CALLBACK(manualsearch_close_cb), mdata);
  g_signal_connect(G_OBJECT(mdata->ok), "clicked",
				   G_CALLBACK(manualsearch_ok_cb), mdata);
  g_signal_connect(G_OBJECT(mdata->apply), "clicked",
				   G_CALLBACK(manualsearch_apply_cb), mdata);


  return mdata;
}


static void manual_search_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  static  struct manual_search_data *mdata = NULL;

  if(! mdata)  {
	mdata = load_manual_search_dialog(ui_data);
	if(! mdata)
	  return;
  }

  setup_manual_search_dialog(mdata);

  gtk_widget_show_all(mdata->mwin);
}

static void status_icon_activate(GtkStatusIcon *si, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;

  if(ui_data->manual_search_available)  {
	ui_data->manual_search_available = FALSE;
	manual_search_cb(NULL, data);
  }
}

static   void show_lyric_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  gboolean stat;

  stat = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item));
  ui_data->hide = ! stat;
}

static   void auto_select_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;

  ui_data->autoselect = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item));
}

static   void set_color_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  gint response;
  GdkColor color;
  guint16 alpha;
  GtkWidget *dialog;
  GtkColorSelection *colorsel;

  color.red = (guint16)(ui_data->r * (guint16)(-1));
  color.green = (guint16)(ui_data->g * (guint16)(-1));
  color.blue = (guint16)(ui_data->b * (guint16)(-1));
  alpha = (guint16)(ui_data->alpha * (guint16)(-1));
  dialog = gtk_color_selection_dialog_new("Select Lyric Color");
  colorsel = GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(dialog)->colorsel);
  gtk_color_selection_set_has_opacity_control(colorsel, TRUE);
  gtk_color_selection_set_current_color(colorsel, &color);
  gtk_color_selection_set_current_alpha(colorsel, alpha);
  response = gtk_dialog_run(GTK_DIALOG(dialog));
  if(response == GTK_RESPONSE_OK)  {
	gtk_color_selection_get_current_color(colorsel, &color);
	alpha = gtk_color_selection_get_current_alpha(colorsel);
	ui_data->r = (double)(color.red)/(guint16)(-1);
	ui_data->g = (double)(color.green)/(guint16)(-1);
	ui_data->b = (double)(color.blue)/(guint16)(-1);
	ui_data->alpha = (double)(alpha)/(guint16)(-1);
	ui_data->shadow_alpha = ui_data->alpha - 0.2;
	if(ui_data->shadow_alpha < 0)
	  ui_data->shadow_alpha = 0;
	if(ui_data->config)  {
	  set_config_valtype(ui_data->config, double, NULL, "color_red", ui_data->r);
	  set_config_valtype(ui_data->config, double, NULL, "color_green", ui_data->g);
	  set_config_valtype(ui_data->config, double, NULL, "color_blue", ui_data->b);
	  set_config_valtype(ui_data->config, double, NULL, "color_alpha", ui_data->alpha);
	}
  }

  gtk_widget_destroy(dialog);
}


struct reference_data{
  struct ui_data *ui_data;
  GladeXML *gxml;
  GtkWidget *mwin;
  GtkWidget *colorbutton;
  GtkWidget *fontbutton;
  GtkWidget *opacity;
  GtkWidget *autoselect_button;
  GtkWidget *showlyric_button;
  GtkWidget *drawshadow_button;
  GtkWidget *timeout_label;
  GtkWidget *timeout_button;
  GtkWidget *ok_button;
  GtkWidget *cancel_button;
};


static void autoselect_button_toggled(GtkToggleButton *b, gpointer data)
{
  struct reference_data *rdata = (struct reference_data *)data;
  gboolean stat;

  stat = gtk_toggle_button_get_active(b);
  if(stat)  {
	gtk_widget_set_state(rdata->timeout_label, GTK_STATE_INSENSITIVE);
	gtk_widget_set_state(rdata->timeout_button, GTK_STATE_INSENSITIVE);
  }else  {
	gtk_widget_set_state(rdata->timeout_label, GTK_STATE_NORMAL);
	gtk_widget_set_state(rdata->timeout_button, GTK_STATE_NORMAL);
  }

}


static   void cancel_button_clicked(GtkButton *b, gpointer data)
{
  struct reference_data *rdata = (struct reference_data *)data;

  gtk_widget_destroy(rdata->mwin);
  g_object_unref(rdata->gxml);
  g_free(rdata);
}

static gboolean reference_delete_event(GtkWidget *w, GdkEvent *e, gpointer data)
{
  struct reference_data *rdata = (struct reference_data *)data;

  cancel_button_clicked(NULL, rdata);
}


static   void ok_button_clicked(GtkButton *b, gpointer data)
{
  struct reference_data *rdata = (struct reference_data *)data;
  struct ui_data *ui_data = rdata->ui_data;
  GdkColor color;
  const gchar *font;

  gtk_color_button_get_color(GTK_COLOR_BUTTON(rdata->colorbutton), &color);
  ui_data->alpha = gtk_range_get_value(GTK_RANGE(rdata->opacity));
  ui_data->r = (double)(color.red)/(guint16)(-1);
  ui_data->g = (double)(color.green)/(guint16)(-1);
  ui_data->b = (double)(color.blue)/(guint16)(-1);
  ui_data->shadow_alpha = ui_data->alpha - 0.2;
  if(ui_data->shadow_alpha < 0)
	ui_data->shadow_alpha = 0;
  if(ui_data->config)  {
	set_config_valtype(ui_data->config, double, NULL, "color_red", ui_data->r);
	set_config_valtype(ui_data->config, double, NULL, "color_green", ui_data->g);
	set_config_valtype(ui_data->config, double, NULL, "color_blue", ui_data->b);
	set_config_valtype(ui_data->config, double, NULL, "color_alpha", ui_data->alpha);
  }
  font = gtk_font_button_get_font_name(GTK_FONT_BUTTON(rdata->fontbutton));
  if(g_strcmp0(font, ui_data->font))  {
	g_free(ui_data->font);
	ui_data->font = g_strdup(font);
	ui_data->font_changed = TRUE;
  }

  ui_data->autoselect = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rdata->autoselect_button));
  if(! ui_data->autoselect)
	ui_data->select_timeout = (gint)gtk_spin_button_get_value(GTK_SPIN_BUTTON(rdata->timeout_button));

  ui_data->hide = ! gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rdata->showlyric_button));
  ui_data->with_shadow = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(rdata->drawshadow_button));

  set_config(ui_data->config, NULL, "font", ui_data->font);
  set_config_valtype(ui_data->config, boolean, NULL, "autoselect", ui_data->autoselect);
  set_config_valtype(ui_data->config, integer, NULL, "select_timeout", ui_data->select_timeout);
  set_config_valtype(ui_data->config, boolean, NULL, "with_shadow", ui_data->with_shadow);

  sync_config(ui_data->config);

  cancel_button_clicked(NULL, rdata);
}


static   void preferences_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  struct reference_data *rdata;
  GladeXML *gxml;
  GtkWidget *mwin;
  GtkWidget *colorbutton;
  GtkWidget *fontbutton;
  GtkWidget *opacity;
  GtkWidget *autoselect_button;
  GtkWidget *showlyric_button;
  GtkWidget *drawshadow_button;
  GtkWidget *timeout_label;
  GtkWidget *timeout_button;
  GtkWidget *ok_button;
  GtkWidget *cancel_button;
  gchar *xmlpath;
  GdkColor color;

  xmlpath = g_build_filename(LYRICPLAYER_GLADEDIR, "preferences.glade", NULL);
  gxml = glade_xml_new(xmlpath, NULL, NULL);
  if(! gxml)  {
	dialog_warn("Failed load glade UI: %s", xmlpath);
	g_free(xmlpath);
	return;
  }

  color.red = (guint16)(ui_data->r * (guint16)(-1));
  color.green = (guint16)(ui_data->g * (guint16)(-1));
  color.blue = (guint16)(ui_data->b * (guint16)(-1));

  mwin = glade_xml_get_widget(gxml, "preferences");
  colorbutton = glade_xml_get_widget(gxml, "colorbutton");
  fontbutton = glade_xml_get_widget(gxml, "fontbutton");
  opacity = glade_xml_get_widget(gxml, "alpha");
  autoselect_button = glade_xml_get_widget(gxml, "auto_select");
  showlyric_button = glade_xml_get_widget(gxml, "show_lyric");
  drawshadow_button = glade_xml_get_widget(gxml, "with_shadow");
  timeout_label = glade_xml_get_widget(gxml, "label_timeout");
  timeout_button = glade_xml_get_widget(gxml, "select_timeout");
  ok_button = glade_xml_get_widget(gxml, "ok");
  cancel_button = glade_xml_get_widget(gxml, "cancel");

  gtk_color_button_set_color(GTK_COLOR_BUTTON(colorbutton), &color);
  if(ui_data->font)
	gtk_font_button_set_font_name(GTK_FONT_BUTTON(fontbutton), ui_data->font);
  gtk_range_set_value(GTK_RANGE(opacity), ui_data->alpha);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autoselect_button),
							   ui_data->autoselect);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(showlyric_button),
							   ! ui_data->hide);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(drawshadow_button),
							   ui_data->with_shadow);
  if(ui_data->autoselect)  {
	gtk_widget_set_state(timeout_label, GTK_STATE_INSENSITIVE);
	gtk_widget_set_state(timeout_button, GTK_STATE_INSENSITIVE);
  }else  {
	gtk_widget_set_state(timeout_label, GTK_STATE_NORMAL);
	gtk_widget_set_state(timeout_button, GTK_STATE_NORMAL);
  }

  rdata = g_new(struct reference_data, 1);
  rdata->ui_data = ui_data;
  rdata->gxml = gxml;
  rdata->mwin = mwin;
  rdata->colorbutton = colorbutton;
  rdata->fontbutton = fontbutton;
  rdata->opacity = opacity;
  rdata->autoselect_button = autoselect_button;
  rdata->showlyric_button = showlyric_button;
  rdata->drawshadow_button = drawshadow_button;
  rdata->timeout_label = timeout_label;
  rdata->timeout_button = timeout_button;
  rdata->ok_button = ok_button;
  rdata->cancel_button = cancel_button;

  glade_xml_signal_autoconnect(gxml);
  g_signal_connect(G_OBJECT(autoselect_button), "toggled",
				   G_CALLBACK(autoselect_button_toggled), rdata);
  g_signal_connect(G_OBJECT(ok_button), "clicked",
				   G_CALLBACK(ok_button_clicked), rdata);
  g_signal_connect(G_OBJECT(cancel_button), "clicked",
				   G_CALLBACK(cancel_button_clicked), rdata);
  g_signal_connect(G_OBJECT(mwin), "delete-event",
				   G_CALLBACK(reference_delete_event), rdata);

  gtk_widget_show_all(mwin);
}

static   void help_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;

  gnome_url_show(PROG_WEBSITE, NULL);
}

static   void about_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  GtkWidget *about;
  GdkPixbuf *logo;

  logo = gdk_pixbuf_new_from_file(ICON_PATH, NULL);
  gtk_show_about_dialog(NULL,
						"program-name", PROG_NAME, 
						"version", PROG_VERSION,
						"copyright", PROG_COPYRIGHT,
						"comments", PROG_COMMENTS,
						"website", PROG_WEBSITE,
						"logo", logo,
						NULL);
}

static gboolean ui_gosd_cleanup(void *data);

static   void quit_cb(GtkMenuItem *item, gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;

  ui_gosd_cleanup(ui_data);
  exit(0);
}



static void status_icon_popup_menu(GtkStatusIcon *si,
								   guint button,
								   guint activate_time,
								   gpointer data)
{
  GtkWidget *menu = (GtkWidget *)data;

  gtk_widget_show_all(menu);
  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
				 button, activate_time);
}


static void   gtk_status_icon_init_menu(GtkStatusIcon *si, struct ui_data *ui_data)
{
  GtkWidget *menu;
  GtkWidget *prev;
  GtkWidget *toggle;
  GtkWidget *next;
  GtkWidget *separator;
  GtkWidget *manualsearch;
  GtkWidget *showlyric;
  GtkWidget *autoselect;
  GtkWidget *setcolor;
  GtkWidget *separator1;
  GtkWidget *preferences;
  GtkWidget *help;
  GtkWidget *about;
  GtkWidget *separator2;
  GtkWidget *quit;
  gint status;

  menu = gtk_menu_new();
  g_signal_connect(G_OBJECT(si), "activate",
				   G_CALLBACK(status_icon_activate), ui_data);
  g_signal_connect(G_OBJECT(si), "popup-menu",
				   G_CALLBACK(status_icon_popup_menu), menu);
  prev = gtk_image_menu_item_new_from_stock(GTK_STOCK_MEDIA_PREVIOUS, NULL);
  g_signal_connect(G_OBJECT(prev), "activate",
				   G_CALLBACK(prev_cb), ui_data);
  status = (ui_data->client->status)(ui_data->client->c_data);
  if(status == PLAYER_STATUS_PLAY)
	toggle = gtk_image_menu_item_new_from_stock(GTK_STOCK_MEDIA_PAUSE, NULL);
  else
	toggle = gtk_image_menu_item_new_from_stock(GTK_STOCK_MEDIA_PLAY, NULL);
  g_signal_connect(G_OBJECT(toggle), "activate",
				   G_CALLBACK(toggle_cb), ui_data);
  next = gtk_image_menu_item_new_from_stock(GTK_STOCK_MEDIA_NEXT, NULL);
  g_signal_connect(G_OBJECT(next), "activate",
				   G_CALLBACK(next_cb), ui_data);
  separator = gtk_separator_menu_item_new();;
  manualsearch = gtk_image_menu_item_new_with_label("search manual");
  g_signal_connect(G_OBJECT(manualsearch), "activate",
				   G_CALLBACK(manual_search_cb), ui_data);
  showlyric = gtk_check_menu_item_new_with_label("show lyric");
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(showlyric), ! ui_data->hide);
  g_signal_connect(G_OBJECT(showlyric), "activate",
				   G_CALLBACK(show_lyric_cb), ui_data);
  autoselect = gtk_check_menu_item_new_with_label("auto select lyric");
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(autoselect), ui_data->autoselect);
  g_signal_connect(G_OBJECT(autoselect), "activate",
				   G_CALLBACK(auto_select_cb), ui_data);
  setcolor = gtk_image_menu_item_new_with_label("set color");
  g_signal_connect(G_OBJECT(setcolor), "activate",
				   G_CALLBACK(set_color_cb), ui_data);
  separator1 = gtk_separator_menu_item_new();
  preferences = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
  g_signal_connect(G_OBJECT(preferences), "activate",
				   G_CALLBACK(preferences_cb), ui_data);
  help = gtk_image_menu_item_new_from_stock(GTK_STOCK_HELP, NULL);
  g_signal_connect(G_OBJECT(help), "activate",
				   G_CALLBACK(help_cb), ui_data);
  about = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL);
  g_signal_connect(G_OBJECT(about), "activate",
				   G_CALLBACK(about_cb), ui_data);
  separator2 = gtk_separator_menu_item_new();
  quit = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
  g_signal_connect(G_OBJECT(quit), "activate",
				   G_CALLBACK(quit_cb), ui_data);

  gtk_menu_append(GTK_MENU(menu), prev);
  gtk_menu_append(GTK_MENU(menu), toggle);
  gtk_menu_append(GTK_MENU(menu), next);
  gtk_menu_append(GTK_MENU(menu), separator);
  gtk_menu_append(GTK_MENU(menu), manualsearch);
  gtk_menu_append(GTK_MENU(menu), showlyric);
  gtk_menu_append(GTK_MENU(menu), autoselect);
  gtk_menu_append(GTK_MENU(menu), setcolor);
  gtk_menu_append(GTK_MENU(menu), separator1);
  gtk_menu_append(GTK_MENU(menu), preferences);
  gtk_menu_append(GTK_MENU(menu), help);
  gtk_menu_append(GTK_MENU(menu), about);
  gtk_menu_append(GTK_MENU(menu), separator2);
  gtk_menu_append(GTK_MENU(menu), quit);
}


static void render_lyric(Ghosd *ghosd, cairo_t *cr, void *data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  fprintf(stderr, "COLORS: r:%f g:%f b:%f A:%f S:%f\n", ui_data->r, ui_data->g,
		  ui_data->b, ui_data->alpha, ui_data->shadow_alpha);

  if(ui_data->with_shadow)  {
	cairo_set_source_rgba(cr, ui_data->r, ui_data->g,
						  ui_data->b, ui_data->shadow_alpha);
	cairo_move_to(cr, 2, 2);
	pango_cairo_show_layout(cr, ui_data->layout);
  }

  cairo_set_source_rgba(cr, ui_data->r, ui_data->g, 
						ui_data->b, ui_data->alpha);
  cairo_move_to(cr, 2, 2);
  pango_cairo_show_layout(cr, ui_data->layout);
}


static void *thread_main(void *data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  struct timespec ts = { 0, 15000000 };
  struct timespec hide = { 0, 80000000 };
  gint magic;
  lyric_t *lyric = NULL;
  gint begin, end;
  gint fade;
  gint now;
  gchar *lyric_text;
  PangoFontDescription *desc;
  PangoFontMap *fontmap;
  PangoContext *context;
  PangoLayout *layout;

  fontmap = pango_cairo_font_map_get_default();
  context = pango_cairo_font_map_create_context(PANGO_CAIRO_FONT_MAP(fontmap));
  layout = pango_layout_new(context);
  if(ui_data->font)  {
	desc = pango_font_description_from_string(ui_data->font);
	pango_layout_set_font_description(layout, desc);
  }

  ui_data->layout = layout; 
  for(;;)  {
	if(ui_data->backend_status != PLAYER_STATUS_PLAY ||
	   ui_data->no_lyric )  {
	  g_object_unref(layout);
	  ui_data->thread = NULL;
	  return NULL;
	}

	magic = ui_data->magic;
	if(! ui_data->lyric)  {
	  lyric = pick_right_lyric(ui_data, "utf-8");
	  if(! lyric)  {
		ui_data->no_lyric = TRUE;
		break;
	  }
	}
	else
	  print_verb("using manual searched lyric.\n");

	if(magic != ui_data->magic)
	  continue;

	for(;;)  {
	  if(ui_data->lyric)  {
		if(lyric)
		  lyricparse_release_lyric(lyric);
		lyric = ui_data->lyric;
		ui_data->lyric = NULL;
	  }

	  if(ui_data->font_changed)  {
		ui_data->font_changed = FALSE;
		if(ui_data->font)  {
		  desc = pango_font_description_from_string(ui_data->font);
		  pango_layout_set_font_description(layout, desc);
		}else  {
		  g_object_unref(layout);
		  fontmap = pango_cairo_font_map_get_default();
		  context = pango_cairo_font_map_create_context(PANGO_CAIRO_FONT_MAP(fontmap));
		  layout = pango_layout_new(context);
		}
	  }

	  while(xmms2_lock)
		nanosleep(&ts, NULL);
	  xmms2_lock++;
	  now = (ui_data->client->time)(ui_data->client->c_data);
	  xmms2_lock--;

	  if(! lyricparse_get_lyric(lyric, &lyric_text, &begin, &end, now))  {
		ui_data->no_lyric = TRUE;
		g_object_unref(layout);
		ui_data->thread = NULL;
		return NULL;
	  }

	  if(end == 0)  {
		end = begin + 5;
		ui_data->no_lyric = TRUE;
	  }

	  if(magic != ui_data->magic)
		break;

	  fade = 150;
	  if(end - begin < fade * 2)
		fade = (end - begin) / 2;
	  if(! ui_data->hide)  {
		ui_data->ghosd = ghosd_new();
		pango_layout_set_text(layout, lyric_text, -1);
		ghosd_text_set_position(ui_data->ghosd, ui_data->x, ui_data->y, layout);
		ghosd_set_render(ui_data->ghosd, render_lyric, ui_data);
		ghosd_flash(ui_data->ghosd, fade, end - begin);
		ghosd_free(ui_data->ghosd);
	  } else
		nanosleep(&hide, NULL);

	  if(ui_data->no_lyric || ui_data->magic != magic)
		break;
	}

	lyricparse_release_lyric(lyric);
	lyric = NULL;
  }

  g_object_unref(layout);
  ui_data->thread = NULL;
  return NULL;
}

static gboolean spawn_osd_thread(struct ui_data *ui_data)
{
  if(! ui_data->thread)  {
	print_verb("spwning lyric osd thread..\n");
	ui_data->thread = g_thread_create(thread_main, ui_data, TRUE, NULL);
	if(! ui_data->thread)  {
	  dialog_warn("Fatal! error spawning OSD thread!");
	  exit(1);
	}
  }

  return TRUE;
}


static gboolean timeout_callback(gpointer data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  gchar *playing_file;
  static gboolean notified = FALSE;
  gint status;

  if(xmms2_lock)  return TRUE;
  xmms2_lock++;
  status = (ui_data->client->status)(ui_data->client->c_data);
  xmms2_lock--;
  ui_data->backend_status = status;
  if(status != PLAYER_STATUS_PLAY)
	return TRUE;

  if(xmms2_lock)  return TRUE;
  xmms2_lock++;
  playing_file = (ui_data->client->get_music_file)(ui_data->client->c_data);
  xmms2_lock--;
  if(playing_file) {
	if(playing_file == (gchar *)-1)
	  return TRUE;
	if(! ui_data->playing_file)
	  ui_data->playing_file = playing_file;
	else	if(strcmp(ui_data->playing_file, playing_file))  {
	  g_free(ui_data->playing_file);
	  ui_data->playing_file = playing_file;
	  ui_data->no_lyric = FALSE;
	  ui_data->magic++;
	  notified = FALSE;
	}else
	  g_free(playing_file);
  }

  if(! ui_data->no_lyric || ui_data->lyric)  {
	if(! ui_data->thread)
	  if(! spawn_osd_thread(ui_data))  {
		dialog_warn("Fatal! unable to spawn OSD thread!");
		exit(1);
	  }
  }else  if(! notified)  {
	notify_user(ui_data, "No Lyric Searched!", 0, "");
	notified = TRUE;
  }

  return TRUE;
}

static gint ui_gosd_run(void *data)
{
  GtkStatusIcon *si;
  const gchar *icon = ICON_PATH;
  struct ui_data *ui_data = (struct ui_data *)data;

  if(! g_file_test(icon, G_FILE_TEST_EXISTS))  {
	dialog_warn("icon file don't exist: %s", icon);
	return 1;
  }

  si = gtk_status_icon_new_from_file(icon);
  gtk_status_icon_init_menu(si, ui_data);
  gtk_status_icon_set_visible(si, TRUE);
  gtk_timeout_add(50, timeout_callback, ui_data);

  ui_data->si = si;

  gtk_main();

  return 0;
}


static gboolean  ui_gosd_cleanup(void *data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  gchar **client_name;

  fetch_lyric_unload_all(&ui_data->fl);
  client_name = list_client();
  if(! client_name || ! *client_name)
	dialog_warn("player frontend plugin internal error!");
  if(client_name)
	if(*client_name)  {
	  unload_client(*client_name, ui_data->client);
	  g_strfreev(client_name);
	}
  sync_config(ui_data->config);
  free_config(ui_data->config);
  g_free(ui_data->playing_file);
  g_free(ui_data->font);
  g_free(data);

  return TRUE;
}


static struct ui *ui_gosd_init(const gchar *args, gint argc, gchar **argv)
{
  struct ui *ui;
  struct ui_data *data;
  config_t *config;
  struct client *client;

  if(! g_thread_supported())
	g_thread_init(NULL);

  if(! gtk_init_check(&argc, &argv))  {
	print_err("failed initializing GTK+.\n");
	return NULL;
  }

  notify_init(PROG_NAME);

  config = load_config(NULL, "ui", "ui_gosd.conf", UI_GOSD_DEFAULT_CONFIG, 0, TRUE);
  data = g_new0(struct ui_data, 1);

  data->config = config;
  data->autoselect = TRUE;
  data->r = 0;
  data->g = 0;
  data->b = 0;
  data->alpha = 0.8;
  data->with_shadow = TRUE;
  data->x = -50;
  data->y = -50;
  data->select_timeout = 10;

  if(config)  {
	read_config(config, NULL, "font", &data->font, "Sans 28", TRUE);
	read_config_valtype(config, data->autoselect, boolean, NULL, "autoselect");
	read_config_valtype(config, data->x, integer, NULL, "position_x");
	read_config_valtype(config, data->y, integer, NULL, "position_y");
	read_config_valtype(config, data->r, double, NULL, "color_red");
	read_config_valtype(config, data->g, double, NULL, "color_green");
	read_config_valtype(config, data->b, double, NULL, "color_blue");
	read_config_valtype(config, data->alpha, double, NULL, "color_alpha");
	read_config_valtype(config, data->with_shadow, boolean, NULL, "with_shadow");
	read_config_valtype(config, data->select_timeout, integer, NULL, "select_timeout");
  }
  if(data->x == 0)  {
	data->x = -50;
	set_config_valtype(config, integer, NULL, "position_x", data->x);
  }

  if(data->y == 0)  {
	data->y = -50;
	set_config_valtype(config, integer, NULL, "position_y", data->y);
  }

  if(data->alpha < 0.15)  {
	data->alpha == 0.2;
	set_config_valtype(config, double, NULL, "color_alpha", data->alpha);
  }

  if(data->select_timeout == 0)  {
	data->select_timeout = 5;
	set_config_valtype(config, integer, NULL, "select_timeout", data->select_timeout);
  }

  data->shadow_alpha = data->alpha - 0.2;
  if(data->shadow_alpha <= 0)  {
	data->shadow_alpha = 0;
	data->with_shadow = FALSE;
  }

  if(! fetch_lyric_load_all(&data->fl, NULL, argc, argv))  {
	dialog_warn("unable to load any lyric searching plugin!");
	goto err_lyric;
  }

  client = load_and_init_client("XMMS2", PROG_NAME, NULL, argc, argv);
  if(! client)  {
	dialog_warn("can't initialize player backend!");
	goto err_client;
  }
  data->client = client;

  ui = g_new(struct ui, 1);
  ui->ui_run = ui_gosd_run;
  ui->ui_cleanup = ui_gosd_cleanup;
  ui->data = data;

  return ui;

 err_client:
  fetch_lyric_unload_all(&data->fl);
 err_lyric:
  free_config(config);
  g_free(data);

  return NULL;
}


struct  ui_descrip UI_EXPORT_SYMBOL =  {
  .ui_init = ui_gosd_init,

  .name = "gosd",
  .description = "gnome OSD interface plugin for lyric player",
  .author = "X Q",
  .mail = "crs.chin@gmail.com",
  .version = "0.1"
};
